/*
 * Copyright (c) 2017-2019, Lindenis Tech. Ltd.
 * All rights reserved.
 *
 * File:
 *
 * Description:
 *
 * Author:
 *      xiaoshujun@lindeni.com
 *
 * Create Date:
 *      2019/08/08
 *
 * History:
 *
 */

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

#include "lindplayer.h"
#include "osal_common.h"
#include "osal_mutex.h"
#include "osal_cond.h"
#include "lindzcfg.h"

#define LOG_TAG "lindplayer"
#include "osal_log.h"

static int sig_error = 0;

typedef struct _lindplayer_ctx_t
{
    player_handle_t         player;
    lindplayer_config_t     config;
    pthread_t               input_tid;

    char                    url[256];

    _handle_t               h_zcfg;
    zcfg_config_t           zcfg_config;

    int                     ui_show;

    int                     eof;
    int                     sig_error;

    OSAL_mutex *            mutex;
    OSAL_cond *             cond;
}lindplayer_ctx_t;

static void signal_crash_handler(int sig)
{
    logd("exception sig(%d) catched, notify app exit", sig);

    sig_error = 1;
}

static int on_input_key(lindplayer_ctx_t * p_ctx)
{
    player_handle_t player = p_ctx->player;
    signed char key_val;
    int ret = wait_input_char(&key_val, 500000);    // 500ms time out
    if (ret < 0)
    {
        return -1;
    }
    else if (ret == 0)
    {
        if (p_ctx->eof || sig_error)
        {
            return -1;
        }
        return 0;
    }

    if (key_val > 0 && key_val)
    {
        if (key_val == '\n')
        {
            printf("\n");
        }
        else
        {
            printf("********* key_val: %c ********** \n", key_val);
        }

        if (key_val == 'q' || key_val == 'Q')
        {
            player_stop(player);
            return -1;
        }
        else if (key_val == 'v')
        {
            logd("version: %s", get_player_version());
            return 0;
        }
        else if (key_val == 'p')
        {
            // pause
            player_pause2(player, 1);
            return 0;
        }
        else if (key_val == 'P')
        {
            // resume
            player_pause2(player, 0);
            return 0;
        }
        else if (key_val == 's')
        {
            int64_t stop_us = 10000000;  // forward 10s
            player_seek(player, 1, stop_us);
            return 0;
        }
        else if (key_val == 'S')
        {
            int64_t stop_us = 10000000;  // backward 10s
            player_seek(player, 0, stop_us);
            return 0;
        }
        else if (key_val == 'g')
        {
            int64_t pos = player_get_cur_pos(player);
            logd("cur pos: %lld(ms)", pos / 1000);
            return 0;
        }
        else if (key_val == 'G')
        {
            int64_t duration = player_get_duration(player);
            logd("duration: %lld(ms)", duration / 1000);
            return 0;
        }
        else if (key_val == 't')
        {
            PlayerStatus_e status = player_get_cur_status(player);
            logd("status: 0x%08x", status);
            return 0;
        }
        else if (key_val == 'h')
        {
            p_ctx->ui_show = p_ctx->ui_show ? 0 : 1;
            player_show_ui(player, p_ctx->ui_show);
            return 0;
        }
        else if (key_val == '1')
        {
            player_set_speed(player, 1, 1);
            logd("set speed 1x");
            return 0;
        }
        else if (key_val == '2')
        {
            player_set_speed(player, 2, 1);
            logd("set speed 2x");
            return 0;
        }
        else if (key_val == '4')
        {
            player_set_speed(player, 4, 1);
            logd("set speed 4x");
            return 0;
        }
        else if (key_val == '@')
        {
            player_set_speed(player, 1, 2);
            logd("set speed 1/2x");
            return 0;
        }
        else if (key_val == '$')
        {
            player_set_speed(player, 1, 4);
            logd("set speed 1/4x");
            return 0;
        }
        else if (key_val == 'r')
        {
            player_stop(p_ctx->player);
            return 0;
        }
        else if (key_val == 'R')
        {
            ret = player_set_data_source(p_ctx->player, p_ctx->url);
            if (ret < 0)
            {
                loge("player_set_data_source failed ");
                return -1;
            }

            ret = player_prepare(p_ctx->player);
            if (ret < 0)
            {
                loge("player_prepare failed ");
                return -1;
            }

            ret = player_start(p_ctx->player);
            if (ret < 0)
            {
                loge("player_start failed ");
                return -1;
            }
            return 0;
        }
    }

    return 0;
}

static void * input_key_thread(void *param)
{
    lindplayer_ctx_t * p_ctx = (lindplayer_ctx_t *)param;

    while(1)
    {
        if (on_input_key(p_ctx))
            break;
    }

    logd("input_thread exit ");

    return 0;
}

int player_cb(void * user, MessageType_e msg, int ext1, int ext2)
{
    lindplayer_ctx_t * p_ctx = (lindplayer_ctx_t *)user;
    player_handle_t player = p_ctx->player;
    switch (msg) {
        case LINDPLAYER_MSG_EOF:
        {
            logd("player msg LINDPLAYER_MSG_EOF");
            player_stop(player);
            p_ctx->eof = 1;
            break;
        }
        default:
            loge("unknown msg type: %d", msg);
            break;
    }

    return 0;
}

int player_running(lindplayer_ctx_t * p_ctx)
{
    int ret = 0;

    p_ctx->player = player_create();
    if (p_ctx->player == (player_handle_t)NULL)
    {
        loge("player_create failed");
        return -1;
    }

    p_ctx->config.cb = player_cb;
    p_ctx->config.user = (void*)p_ctx;
    player_set_parameters(p_ctx->player, &p_ctx->config);

    ret = player_set_data_source(p_ctx->player, p_ctx->url);
    if (ret < 0)
    {
        loge("player_set_data_source failed ");
        goto __prepare_failed__;
    }

    ret = player_prepare(p_ctx->player);
    if (ret < 0)
    {
        loge("player_prepare failed ");
        goto __prepare_failed__;
    }

    ret = player_start(p_ctx->player);
    if (ret < 0)
    {
        loge("player_start failed ");
        goto __start_failed__;
    }

    ret = pthread_create(&p_ctx->input_tid, NULL, input_key_thread, (void *)p_ctx);
    if (ret) {
        loge("pthread_create msg thread fail! ret: %d ", ret);
        goto __start_failed__;
    }

    pthread_join(p_ctx->input_tid, 0);

    logd("now player to exit ... ");

__start_failed__:
    player_stop(p_ctx->player);

__prepare_failed__:
    player_destroy(p_ctx->player);

    logd("bye bye ");

    return 0;
}

static int _lindplayer_zcfg_cb(void * user, int msg, void * data, int size)
{
    lindplayer_ctx_t * p_ctx = (lindplayer_ctx_t *)user;
    _handle_t h_zcfg = p_ctx->h_zcfg;
    logv("msg: 0x%x, size: %d, data: %s", msg, size, (char *)data);
    switch (msg)
    {
        case ZCFG_SRV_RTSP_URL:
        {
            OSAL_LockMutex(p_ctx->mutex);
            strcpy(p_ctx->url, (char *)data);
            OSAL_CondSignal(p_ctx->cond);
            OSAL_UnlockMutex(p_ctx->mutex);
            break;
        }
        default:
            logv("unknown msg type");
            break;
    }

    return 0;
}

static void print_usage(char * this)
{
    printf("\033[32m");
    printf("usage: \n");
    printf("    %s <url> <options>\n", this);
    printf("        url             : 'url' is the media file/stream to playback.\n");
    printf("                        : If 'url' is set to '-zcfg', the player will try to get a lindeni rtsp server url by 'lindzcfg'.\n");
    printf("                        : Of course the lindeni rtsp server should run first.\n");
    printf("    other options parameters:\n");
    printf("        -p [x y w h]    : set the playback display position on the screen.\n");
    printf("        -r [rotate]     : set the display rotate degree, '0, 90, 180, 270'.\n");
    printf("        -z [z-order]    : set the z-order of the display layer, range: [0, 16].\n");
    printf("        -c [channel]    : set the channel of the display layer, range: [0, 1].\n");
    printf("        -live           : indicate this is a live stream.\n");
    printf("        -tcp            : rtsp stream use tcp transport, if this is not set default use udp.\n");
    printf("        -an             : playback ignore audio stream.\n");
    printf("        -vn             : playback ignore video stream.\n");
    printf("    such as: %s movie.ts -r 90\n", this);
    printf("         as: %s -zcfg\n", this);
    printf("         or: %s http://192.168.1.102/test\n", this);
    printf("         or: %s rtsp://192.168.1.102/test -live\n", this);
    printf("\033[0m\n");
}

int main(int argc, char *argv[])
{
    int i = 0;
    int ret = 0;
    int b_url_zcfg = 0;
    lindplayer_ctx_t context;

    signal(SIGINT , signal_crash_handler);      // ctrl+c, Interrupt (ANSI)
    signal(SIGTERM, signal_crash_handler);      // kill, Termination (ANSI)
    signal(SIGBUS, signal_crash_handler);       // bus error
    signal(SIGSEGV, signal_crash_handler);      // SIGSEGV(11), illegal memory access
    signal(SIGFPE, signal_crash_handler);       // SIGFPE, math error, such as div zero
    signal(SIGABRT, signal_crash_handler);      // SIGABR, process calling abort

    memset(&context, 0, sizeof(lindplayer_ctx_t));
    context.config.is_live = 0;
    strcpy(context.config.transport, "udp");
    context.config.z_order = 5;
    context.config.channel_id = 0;
    context.ui_show = 1;

    context.mutex = OSAL_CreateMutex();
    context.cond = OSAL_CreateCond();

    if (argc == 1 || !strcmp(argv[1], "-h"))
    {
        print_usage(argv[0]);
        return 0;
    }
    else
    {
        if (!strcmp(argv[1], "-zcfg")) {
            b_url_zcfg = 1;
        } else {
            strcpy(context.url, argv[1]);
        }

        for (i = 2; i < argc;)
        {
            if (!strcmp(argv[i], "-live"))
            {
                context.config.is_live = 1;
                i += 1;
            }
            else if (!strcmp(argv[i], "-tcp"))
            {
                strcpy(context.config.transport, "tcp");
                i += 1;
            }
            else if (!strcmp(argv[i], "-r"))
            {
                int rotate = atoi(argv[i + 1]);
                i += 2;
                switch(rotate)
                {
                    case 0:
                        context.config.disp_rotate = ROTATE_DEGREE_NONE;
                        break;
                    case 90:
                        context.config.disp_rotate = ROTATE_DEGREE_90;
                        break;
                    case 180:
                        context.config.disp_rotate = ROTATE_DEGREE_180;
                        break;
                    case 270:
                        context.config.disp_rotate = ROTATE_DEGREE_270;
                        break;
                    default:
                        context.config.disp_rotate = ROTATE_DEGREE_NONE;
                        logw("invalid rotate(%d), force to 0", rotate);
                        break;
                }
            }
            else if (!strcmp(argv[i], "-p"))
            {
                context.config.disp_pos.left = atoi(argv[i + 1]);
                context.config.disp_pos.top = atoi(argv[i + 2]);
                context.config.disp_pos.width = atoi(argv[i + 3]);
                context.config.disp_pos.height = atoi(argv[i + 4]);
                i += 5;
            }
            else if (!strcmp(argv[i], "-z"))
            {
                context.config.z_order = atoi(argv[i + 1]);
                i += 2;
            }
            else if (!strcmp(argv[i], "-c"))
            {
                context.config.channel_id = atoi(argv[i + 1]);
                i += 2;
            }
            else if (!strcmp(argv[i], "-an"))
            {
                context.config.disable_audio = 1;
                i += 1;
            }
            else if (!strcmp(argv[i], "-vn"))
            {
                context.config.disable_video = 1;
                i += 1;
            }
            else
            {
                i++;
            }
        }
    }

    if (b_url_zcfg)
    {
        context.config.is_live = 1;

        context.zcfg_config.cb = _lindplayer_zcfg_cb;
        context.zcfg_config.user = &context;
        ret = zcfg_cli_init(&context.h_zcfg, &context.zcfg_config);
        if (ret < 0)
        {
            loge("zcfg_host_init failed");
            return -1;
        }

        OSAL_LockMutex(context.mutex);
        zcfg_cli_query(context.h_zcfg, ZCFG_SRV_RTSP_URL, NULL, 0);
        OSAL_CondWaitTimeout(context.cond, context.mutex, 1000);
        OSAL_UnlockMutex(context.mutex);

        zcfg_cli_deinit(context.h_zcfg);

        if (strlen(context.url) <= 0)
        {
            loge("get lindenis rtsp url failed");
            print_usage(argv[0]);
            return -1;
        }
    }

    if (context.config.is_live) {
        logd("try to play live stream: %s ...", context.url);
    } else {
        logd("try to play file: %s ...", context.url);
    }

    player_running(&context);

    OSAL_DestroyCond(context.cond);
    OSAL_DestroyMutex(context.mutex);

    return 0;
}
